<!DOCTYPE html>
<html lang='en'>

<head>
    <meta charset="utf-8">

    <link href="/css/phylotree.css" rel="stylesheet">

    <link rel="stylesheet" href="/css/bootstrap.min.css">
    <link rel="stylesheet" href="/css/bootstrap-theme.min.css">
  
    <script src="/js/jquery.js"></script>
    <script src="/js/bootstrap.min.js"></script>
    <script src="/js/d3.v3.min.js"></script>
    <script src="/js/underscore-min.js"></script>
    <script src="/phylotree.min.js"></script>
    
    <style>
        .date-text {
            padding: 0.25em;
            text-align: center;
        }
        
        .dot {
          stroke: #000;
          opacity : 0.1;
        }
        
        .size-bubble, .size-bubble * {
            fill: #CCC;
            shape-rendering: crispEdges;
            stroke: black;
            stroke-width: 1px;
        }
        
        .size-label {
            display: block;
            text-align : center;
            font-family: sans-serif;
            font-size: 9pt;
        }

        .fit-label {
            text-anchor : start;
            font-family: sans-serif;
            font-size: 7pt;
        }
    
        .axis text {
            font: 14px sans-serif;
        }

        .axis path, .axis line {
          fill: none;
          stroke: #000;
          shape-rendering: crispEdges;
        }
        
        .fit  {
          fill: none;
          stroke-width: 2px;
          stroke: red;
        } 
        
        .fit-no-counts {
            opacity: 0.1;
            stroke: gray;
        }
           
        @media print
            {    
                .no-print, .no-print *
                {
                    display: none !important;
                }
            }
    </style>
</head>

<body>
 <nav class="navbar navbar-default" role="navigation">
      <!-- Brand and toggle get grouped for better mobile display -->
      <div class="navbar-header">
        <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse">
          <span class="sr-only">Toggle navigation</span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
        </button>
        <a id = "menu-title" class="navbar-brand" href="#"></a>
      </div>
      <div class="collapse navbar-collapse">
        <ul class="nav navbar-nav">
          <li class="dropdown">
            <a href="#" class="dropdown-toggle" data-toggle="dropdown">Tree to display <b class="caret"></b></a>
            <ul class="dropdown-menu" id = 'tree_selector'>
                <li><a href="#" data-tree = "js1774-gag.tre" data-tag = "JS1774 gag"></a></li>
                <li><a href="#" data-tree = "sm1727-gag.tre" data-tag = "SM1727 gag"></a></li>
                <li><a href="#" data-tree = "tm1679-gag.tre" data-tag = "TM1679 gag"></a></li>
                <li><a href="#" data-tree = "js1774-pol-1.tre" data-tag = "JS1774 pol (PR)"></a></li>
                <li><a href="#" data-tree = "js1774-pol-2.tre" data-tag = "JS1774 pol (RT)"></a></li>
                <li><a href="#" data-tree = "sm1727-pol-1.tre" data-tag = "SM1727 pol (PR)"></a></li>
                <li><a href="#" data-tree = "sm1727-pol-2.tre" data-tag = "SM1727 pol (RT)"></a></li>
                <li><a href="#" data-tree = "tm1679-pol-1.tre" data-tag = "TM1679 pol (PR)"></a></li>
                <li><a href="#" data-tree = "tm1679-pol-2.tre" data-tag = "TM1679 pol (RT)"></a></li>
            </ul>
          </li>
          <li>
            <a href="#" data-toggle="modal" data-target="#newick_modal">Input Newick</a>
          </li>
       </ul>
       
        <form class="navbar-form navbar-left">
            <div class="checkbox navbar-btn">
                <label class="navbar-link">
                    <input type="checkbox" id="layout">
                     Radial layout
                </label>
            </div>         
        </form>
      </div><!-- /.navbar-collapse -->

    </nav>

    <div class = "container">
    
    
        <div class = "row" style = "margin-top: 1em">
            <div class = "col-md-2" id="date-legend">
            </div>
            <div class = "col-md-2" id="compartment-legend">
            </div>
            <div class = "col-md-2" id="size-legend">
            </div>
            <div class = "col-md-6">
                 <svg id="rtt_display"></svg>
            </div>
        </div>       
                 
        <div class = "row">
            <div class = "col-md-12">
                <svg id="tree_display"></svg>
            </div>
        </div>
    </div>
        
  
    <div class="modal" id = 'newick_modal'>
      <div class="modal-dialog">
        <div class="modal-content">
          <div class="modal-header">
            <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
            <h4 class="modal-title">Newick string to render</h4>
          </div>
          <div class="modal-body" id = 'newick_body'>
             <textarea id = 'nwk_spec' autofocus = true placeholder = "" style = 'width: 100%; height: 100%' rows = 20 selectionStart = 1 selectionEnd = 1000>(a : 0.1, (b : 0.11, (c : 0.12, d : 0.13) : 0.14) : 0.15)</textarea>
          </div>
          <div class="modal-footer">
            <button type="button" class="btn btn-primary" id = 'validate_newick'>Display this tree</button>
          </div>
        </div><!-- /.modal-content -->
      </div><!-- /.modal-dialog -->
    </div><!-- /.modal -->

    <script>
        // use these formats for parsing and displaying sampling dates
        var date_in = d3.time.format("%Y%m%d"),
            date_out = d3.time.format("%B %d, %Y");
            
        // default scheme to color by date    
        var coloring_scheme = d3.scale.category10();

        // different shapes for labeling compartments
        var compartment_labels = d3.scale.ordinal().range(["circle", "square", "diamond", "cross", "triangle-up"]);
        
        // global tree object
        var tree;
        
        
        // bubble size scale        
        var size_scale = d3.scale.pow ().exponent (0.4).range ([1,6]).clamp (false).domain ([0,1]);
        var rescaled_size;

        // determines the size of a node "bubble"

        function bubbleSize(node) {
            if (node && node.copy_number) {
                return size_scale(node.copy_number);
            }
            return 1;
        }

        function nodeStyler(container, node) {
            if (d3.layout.phylotree.is_leafnode(node)) {

                var existing_circle = container.selectAll("circle");

                if (existing_circle.size() == 1) {
                    existing_circle.remove();
                }
                
                if (node.copy_number) {                
                    existing_circle = container.selectAll("path.node_shape").data([node.compartment]);
                    existing_circle.enter().append("path").classed("node_shape", true);

                    var bubble_size = tree.node_bubble_size(node);

                    var label = existing_circle.attr("d", function(d) {
                        return d3.svg.symbol().type(compartment_labels(d)).size(bubble_size * bubble_size)();
                    }).selectAll ("title").data ([node.copy_number]);
                    label.enter().append("title");
                    label.text ("" + node.copy_number + " copies");


                    existing_circle.style("stroke-width", "1px").style("stroke", "black");
                    if (node.text_angle) {
                        existing_circle.attr("transform", function(d) {
                            return "rotate(" + node.text_angle + ") translate(" + (node.text_align == "end" ? -1 : 1) * bubble_size / 2 + ",0)";
                        });
                    } else {
                        existing_circle.attr("transform", function(d) {
                            return "translate(" + bubble_size / 2 + ",0)";
                        });
                    }
                }

            }

            if (node.date) {
                var node_color = coloring_scheme (node.date);
                container.selectAll("circle").style("fill", node_color);
                container.selectAll("path").style("fill", node_color);
                container.style("fill", node_color);
            }

        }

        function drawATree(newick) {
        
            tree = d3.layout.phylotree()
                .svg(d3.select("#tree_display"))
                .options({
                    'selectable': false,
                    // make nodes and branches not selectable
                    'collapsible': true,
                    // turn off the menu on internal nodes
                    'transitions': false,
                    // turn off d3 animations.
                    'draw-size-bubbles': true,
                    // draw node size bubbles
                    'annular-limit': 0.5,
                    // controls how much of the overall diameter can be taken by 
                    // empty "annular" space
                    'max-radius': 512
                        // maximum radial layout radius
                })
                .node_span(bubbleSize)
                .style_nodes(nodeStyler)
                .node_circle_size (2) // do not draw clickable circles for internal nodes
                .branch_name (function () {return ""}) // no leaf names
                .layout_handler (function (tree) { 
                        plotRTT (tree, "rtt_display", 350,350)
                    }
                )
                ;


            /* the next call creates the tree object, and tree nodes */
            tree(d3.layout.newick_parser(newick));

            /* parse tip names which are assumed to be like this:
                     6_PB_DNA_1_37-52
                 
                     splitting on '_' yields 
                 
                     -- TimePoint; parsed and used to assign *colors* to edges/nodes and to draw root-to-tip distances
                     -- compartment; parsed and used to assign *shapes* to nodes in combination with the DNA/RNA source
                     -- source; 
                     -- clone ID; ignored
                     -- another ID; ignored
                     -- copy number; parsed and used to scale "bubbles" on leaves
                 
                     tips which do not conform to this naming convention are labeled as "reference",
                     and annotated using a special character.
             
                */

            var unique_compartments = {},
                unique_dates = {};
            
            var copy_number_range = [1e100,0],
                attributed_node   = null;

            _.each(tree.get_nodes(), function(value, key) {
                var attributes = value.name.split('_');
                if (attributes.length == 5) {
                    attributed_node = value;
                
                    value.compartment = attributes[1] + ' ' + attributes[2];
                    value.copy_number = parseFloat(attributes[4].split ('-')[1]);
                    
                    copy_number_range[0] = Math.min (copy_number_range[0], value.copy_number);
                    copy_number_range[1] = Math.max (copy_number_range[1], value.copy_number);
                    
                    value.numeric_date = parseInt (attributes[0]);
                    value.date = "Month " + attributes[0];
                    unique_compartments[value.compartment] = 1;
                    unique_dates[value.date] = 1;
                } else {
                    value.is_reference = true;
                }
                
            });
            
            size_scale.domain (copy_number_range);
            coloring_scheme.domain (_.keys(unique_dates).sort());
            compartment_labels.domain (_.keys(unique_compartments).sort());
            
            
            if ($("#layout").prop("checked")) {
                tree.radial (true);
            }
            tree.placenodes().layout();
            
            var rescale = tree.node_bubble_size(attributed_node) / size_scale (attributed_node.copy_number) * 0.5;
            
            rescaled_size = _.compose (function (d) {return d*rescale}, size_scale);

            // create a legend for sizes
            // power of 10 ticks
            
            var size_bubbles = _.map (d3.range (Math.floor (Math.log(copy_number_range[0])/Math.log (10)), Math.ceil (Math.log(copy_number_range[1])/Math.log (10))),
                function (v) {return Math.pow (10,v);});
                
            d3.select ("#size-legend").selectAll (".row").remove();
            var reference_sizes = d3.select ("#size-legend").selectAll (".row").data (_.map (size_bubbles, function (d) {return [d];}));
            reference_sizes.enter().append ("div").classed ("row", true).selectAll (".col-md-2").data (function (d) {return [d];}).enter().append ("div").classed ("col-md-2", true);
            
            var max_size = rescaled_size (size_bubbles[size_bubbles.length - 1]);
            
            reference_sizes.selectAll (".col-md-2").each (function (d) {
                    var circle_size = rescaled_size (d);
                    d3.select (this).append ("svg").attr ("width", 2*max_size + 4).attr ("height", 2*circle_size + 4).append ("circle").classed("size-bubble", true).attr ("r", circle_size).attr ("cx", max_size+2).attr ("cy", circle_size+2);
                    d3.select (this).append ("span").classed ("size-label", true).style ("width", "" + (2*max_size+4) + "px").text (d);
            });
                        
                    
            // create a legend for compartments

            d3.select ("#compartment-legend").selectAll (".row").remove();
            var compartment_depictions = d3.select ("#compartment-legend").selectAll (".row").data (compartment_labels.domain());
            compartment_depictions.enter().append ("div").classed ("row", true).selectAll (".col-md-2").data (function (d) {return [d];}).enter().append ("div").classed ("col-md-2", true);
            compartment_depictions.exit().remove();
            
            compartment_depictions.selectAll (".col-md-2").each (function (d) {
                    d3.select (this).append ("svg").attr ("width", 2*max_size + 4).attr ("height", 2*max_size + 4).append ("path").classed("size-bubble", true).attr("d", function(d) {
                        return d3.svg.symbol().type(compartment_labels(d)).size(max_size * max_size)();
                    }).attr ("transform", "translate( " + max_size + "," + max_size + ")");
                    
                    d3.select (this).append ("span").classed ("size-label", true).style ("width", "" + (2*max_size+4) + "px").text (d);
            });

             // create a legend for dates
            d3.select ("#date-legend").selectAll (".row").remove();
            var date_colors = d3.select ("#date-legend").selectAll (".row").data (coloring_scheme.domain());
            date_colors.enter().append ("div").classed ("row", true);
            date_colors.exit().remove();
            
            date_colors.each (function (d) {
                d3.select(this).style ("background-color", coloring_scheme (d)).classed ("date-text", true).text (d).style ("color", d3.rgb(coloring_scheme (d)).hsl().l < 0.5 ? "white" : "black");
            });

            
            
           // UI handlers
           $("#layout").on("click", function(e) {
                tree.radial($(this).prop("checked")).placenodes().update();
            });

            var best_node = plotRTT (tree, "rtt_display", 350,350, true);
            tree.reroot (best_node).update();
            
        }
        
        function computeRootToTip (node) {
            if (node.parent) {
                node.rtt = node.parent.rtt + tree.branch_length () (node);
            }
            else {
                node.rtt = 0;
            }
            
            if (node.children) {
                for (var c = 0; c < node.children.length; c++) {
                    computeRootToTip (node.children[c]);
                }
                
            }
        }
        
        function computeRootToTipOtherRoot (node, coming_from, shared_distance, distance_to_new_root) {
            var my_bl = tree.branch_length () (node);
        
            var go_up = false;
            if (!coming_from) {
                shared_distance = node.rtt;
                distance_to_new_root = 0;
                go_up = true;
            }
            
            
            if (node.children) {
                for (var c = 0; c < node.children.length; c++) {
                    if (node.children[c] != coming_from) {
                        computeRootToTipOtherRoot (node.children[c], node, shared_distance, distance_to_new_root);
                    } else {
                        go_up = true;
                    }
                }            
            }
            
            node.rtta = node.rtt - shared_distance + distance_to_new_root;
            
            if (go_up) {
                shared_distance      -= my_bl;
                distance_to_new_root += my_bl;
            }
            
            if (node.parent && go_up) {
                computeRootToTipOtherRoot (node.parent, node, shared_distance, distance_to_new_root);
            }
            
        }

        
        function loadTreeFromURL(url) {
            d3.text(url, function(error, newick) {

                drawATree(newick);
            });
        }
        
        function plotRTT (tree, tag, w, h, auto_root) {
            computeRootToTip (tree.get_nodes()[0]);
        
            if (auto_root) {
            
                var max_r2 = 0;
                var best_node = 0;
                tree.traverse_and_compute (function (node) {
                    if (d3.layout.phylotree.is_leafnode (node)) {
                        if (node.numeric_date == 0) {
                            computeRootToTipOtherRoot (node, null, 0, 0);
                            linear_data = [];
                            tree.traverse_and_compute (function (node) {
                                if (d3.layout.phylotree.is_leafnode (node)) {
                                    linear_data.push ([node.numeric_date, node.rtta, node.copy_number]);
                                }
                            });
                            var fit_with_counts = linear_fit (linear_data)["r2"];
                            if (fit_with_counts > max_r2) {
                                max_r2 = fit_with_counts;
                                best_node = node;
                           }
                        }            
                    }
                });
                return best_node;
            }
                        
            var margin = {top: 20, right: 80, bottom: 45, left: 65},
            width = w - margin.left - margin.right,
            height = h - margin.top - margin.bottom;

            var x = d3.scale.linear()
                .range([20, width-20]);

            var y = d3.scale.linear()
                .range([height, 0]);

            var xAxis = d3.svg.axis()
                .scale(x)
                .orient("bottom");

            var yAxis = d3.svg.axis()
                .scale(y)
                .orient("left");
        

            var plot_here = d3.select ("#" + tag);
            plot_here.attr ("width", w).attr("height", h);

            plot_here.selectAll ("g").remove();
    
            var svg = plot_here.append ("g")
                .attr("transform", "translate(" + margin.left + "," + margin.top + ")");    
        
            var plot_data = [];
            
            var linear_data = [];
            
            tree.traverse_and_compute (function (node) {
                if (d3.layout.phylotree.is_leafnode (node)) {
                    plot_data.push (node);
                    linear_data.push ([node.numeric_date, node.rtt, 1]);
                }
            });
            
            xAxis.tickValues (_.keys(_.countBy (linear_data, function (d) {return d[0];})).sort());
            
                
           
     
            x.domain(d3.extent(plot_data, function(d) { return d.numeric_date; })).nice();
            y.domain(d3.extent(plot_data, function(d) { return d.rtt; })).nice();  

            svg.append("g")
                  .attr("class", "x axis")
                  .attr("transform", "translate(0," + height + ")")
                  .call(xAxis)
                .append("text")
                  .attr("class", "label")
                  .attr("x", width)
                  .attr("y", 35)
                  .style("text-anchor", "end")
                  .text("Time");

              svg.append("g")
                  .attr("class", "y axis")
                  .call(yAxis)
                .append("text")
                  .attr("class", "label")
                  .attr("transform", "rotate(-90)")
                  .attr("y", -60)
                  .attr("dy", ".71em")
                  .style("text-anchor", "end")
                  .text("Root-to-tip") ;
          
            svg.selectAll(".dot")
                  .data(plot_data)
                .enter().append("circle")
                  .attr("class", "dot")
                  .attr("r", function (d) {return  2*rescaled_size(tree.node_bubble_size(d));})
                  .attr("cx", function(d) { return x(d.numeric_date); })
                  .attr("cy", function(d) { 
                      return y(d.rtt); })
                  .style("fill", function(d,i) {  return "#000000"; });   
                  
            function linear_fit (data) {
            
                var ss = linear_data.reduce (function (p,c) {return c[2] + p}, 0), // sample count
                    sx = linear_data.reduce (function (p,c) {return c[2]*c[0] + p}, 0), // sum X
                    sy = linear_data.reduce (function (p,c) {return c[2]*c[1] + p}, 0) // sum Y
                    sxoss = sx/ss,
                    syoss = sy/ss;
                    
                var fitB = 0,
                    st2  = 0,
                    vary = 0;
                    
                
                data.forEach (function (point) {
                 
                    var t = point[0] - sxoss; 
                    st2  += point[2] * t * t;
                    fitB += point[2] * t * point[1];
                    vary += point[2] * (point[1]-syoss)*(point[1]-syoss);
                });
                                
                fitB /= st2;
                a = (sy - sx * fitB) / ss;

                var varres = 0;
                
                data.forEach (function (point) {
                    var t = point[1] - a - fitB * point[0];
                    varres += point[2] * t * t;
                });
                
                var normvarres = Math.sqrt (varres / (ss - 2));
                
                return {'intercept' : a,
                        'slope' : fitB,
                        'r2': 1 - varres / vary,
                        'var (intercept)' : Math.sqrt ((1+sx*sx/(ss*st2))/ss),
                        'var (slope)' : Math.sqrt (1/st2)
                        };
                
                
            };

            var fit_with_counts = linear_fit (linear_data);
            
            //console.log (fit_with_counts["R"]);
            
                        
            var label_format = d3.format (".2r");
            
            function format_line (i, s) {
                return label_format (i) + (s >= 0 ? '+' : '') + label_format (s) + '*T';
            }
            
             
             var positive_slope = 0;
                                 
             for (s = 0; s < 1000; s += 1) {
                fit_without_counts = (linear_fit (linear_data.map (function (d) {return [d[0], d[1], Math.floor (d[2]*(0.05 + 9.95*Math.random (0,1)))];})));
                if (fit_without_counts ["slope"] > 0) {
                    positive_slope ++;
                }
                 svg.append ("line")
                               .attr ("x1", x (x.domain()[0]))
                               .attr ("y1", y (x.domain()[0]*fit_without_counts['slope'] + fit_without_counts ['intercept']))
                               .attr ("x2", x (x.domain()[1]))
                               .attr ("y2", y (x.domain()[1]*fit_without_counts['slope'] + fit_without_counts ['intercept']))
                               .attr ("class", "fit fit-no-counts");              
            }
            
            svg.append ("text").attr ("x", x (x.domain()[0])).attr ( y (y.domain [1])).text (d3.format(".2p")(positive_slope/s) + " positive slope");
            
           
            svg.append ("line")
                               .attr ("x1", x (x.domain()[0]))
                               .attr ("y1", y (x.domain()[0]*fit_with_counts['slope'] + fit_with_counts ['intercept']))
                               .attr ("x2", x (x.domain()[1]))
                               .attr ("y2", y (x.domain()[1]*fit_with_counts['slope'] + fit_with_counts ['intercept']))
                               .attr ("class", "fit fit-counts");             
            
            svg.append ("text").attr ("x", x (x.domain()[1]))
                               .attr ("y", y (x.domain()[1]*fit_with_counts['slope'] + fit_with_counts ['intercept'])) 
                               .text (format_line (fit_with_counts['intercept'],fit_with_counts['slope']))
                               .attr ("class", "fit-label")
                               .attr ("dx", "2em")
                               .attr ("dy", "-.25 em"); 

          
        }

        $("#validate_newick").on ("click", function (e) {
            var res = d3.layout.newick_parser ( $('textarea[id$="nwk_spec"]').val(), true);
            if (res["error"] || ! res["json"]) {
                var warning_div = d3.select ("#newick_body").selectAll ("div  .alert-danger").data ([res["error"]])
                warning_div.enter ().append ("div");
                warning_div.html (function (d) {return d;}).attr ("class", "alert-danger");
        
            } else {
                drawATree ($('textarea[id$="nwk_spec"]').val());
                $('#newick_modal').modal('hide');
            }
        });

        $("[data-tree]").each (function () {
            $(this).text ($(this).data("tag"));
        });

        $("[data-tree]").on ("click", function (e) {
            loadTreeFromURL ($(this).data("tree")); 
            $("#menu-title").text ($(this).data("tag"));
        });

        $("[data-tree='tm1679-gag.tre']").click();
    </script>

</body>

</html>
